package com.yiqiniu.easytrans.util;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.aop.support.AopUtils;

import com.yiqiniu.easytrans.protocol.BusinessIdentifer;
import com.yiqiniu.easytrans.protocol.BusinessProvider;
import com.yiqiniu.easytrans.protocol.EasyTransRequest;
import com.yiqiniu.easytrans.protocol.RequestClassAware;

public final class ReflectUtil {
	private ReflectUtil(){
	}
	
	@SuppressWarnings("unchecked")
	public static <P extends EasyTransRequest<R, ?>, R extends Serializable> Class<R> getResultClass(Class<P> paramsClass){
		List<Class<?>> pType = getTypeArguments(EasyTransRequest.class,paramsClass);
		return (Class<R>) pType.get(0);
	}
	
	@SuppressWarnings("unchecked")
	public static Class<? extends EasyTransRequest<?, ?>> getRequestClass(Class<? extends BusinessProvider<?>> providerClass){
	    List<Class<?>> pType = getTypeArguments(BusinessProvider.class, providerClass);
		if(pType != null && pType.size() == 1) {
		    return (Class<? extends EasyTransRequest<?, ?>>) pType.get(0);
		} else {
		    //no generic parameters
		    return null;
		}
	}
	
    @SuppressWarnings("unchecked")
    public static Class<? extends EasyTransRequest<?, ?>> getRequestClass(BusinessProvider<?> provider) {
        if (provider instanceof RequestClassAware) {
            return ((RequestClassAware) provider).getRequestClass();
        } else {
            return getRequestClass((Class<? extends BusinessProvider<?>>) AopUtils.getTargetClass(provider));
        }
    }

	public static <P extends EasyTransRequest<?,?>> BusinessIdentifer getBusinessIdentifer(Class<P> o){
		if(o == null){
			return null;
		}
		return o.getAnnotation(BusinessIdentifer.class);
	}
	
	/**
	 * Get the underlying class for a type, or null if the type is a variable
	 * type.
	 * 
	 * @param type
	 *            the type
	 * @return the underlying class
	 */
	private static Class<?> getClass(Type type) {
		if (type instanceof Class) {
			return (Class<?>) type;
		} else if (type instanceof ParameterizedType) {
			return getClass(((ParameterizedType) type).getRawType());
		} else if (type instanceof GenericArrayType) {
			Type componentType = ((GenericArrayType) type)
					.getGenericComponentType();
			Class<?> componentClass = getClass(componentType);
			if (componentClass != null) {
				return Array.newInstance(componentClass, 0).getClass();
			} else {
				return null;
			}
		} else {
			return null;
		}
	}
	
	
	private static <T> Type getNecessaryResolvedTypes(Class<T> baseClass, Class<? extends T> childClass,Map<Type, Type> resolvedTypes){
		Type targetType = innerGetMap(baseClass, childClass, resolvedTypes);
		if(targetType == null){
			throw new RuntimeException("programe error!");
		}
		return targetType;
	}


	private static <T> Type innerGetMap(Class<T> baseClass, Type childClass, Map<Type, Type> resolvedTypes) {
		Class<?> rawClass = getClass(childClass);
		
		if(rawClass == null){
			return null;
		}
		
		if(rawClass.equals(baseClass)){
			return childClass;
		}

		if (childClass instanceof ParameterizedType) {
			ParameterizedType parameterizedType = (ParameterizedType) childClass;
			Class<?> rawType = (Class<?>) parameterizedType.getRawType();

			Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
			TypeVariable<?>[] typeParameters = rawType.getTypeParameters();
			for (int i = 0; i < actualTypeArguments.length; i++) {
				resolvedTypes.put(typeParameters[i], actualTypeArguments[i]);
			}
		}
		
		{
			Type genericSuperclass = rawClass.getGenericSuperclass();
			if(genericSuperclass != null){
				Class<?> superClass = null;
				if (genericSuperclass instanceof ParameterizedType){
					superClass = (Class<?>) ((ParameterizedType)genericSuperclass).getRawType();
				}else{
					superClass = (Class<?>) genericSuperclass;
				}
				
				if(baseClass.isAssignableFrom(superClass)){
					return innerGetMap(baseClass, genericSuperclass, resolvedTypes);
				}
			}
		}
		
		{
			Type[] genericInterfaces = rawClass.getGenericInterfaces();
			Class<?> superInterface = null;
			for(Type interfaceType :genericInterfaces){
				if(interfaceType instanceof ParameterizedType){
					superInterface = (Class<?>) ((ParameterizedType)interfaceType).getRawType();
				}else{
					superInterface = (Class<?>) interfaceType;
				}
				
				if(baseClass.isAssignableFrom(superInterface)){
					return innerGetMap(baseClass, interfaceType, resolvedTypes);
				}
			}
		}
		
		return null;
	}

	/**
	 * Get the actual type arguments a child class has used to extend a generic
	 * base class.
	 *
	 * @param baseClass
	 *            the base class
	 * @param childClass
	 *            the child class
	 * @return a list of the raw classes for the actual type arguments.
	 */
	public static <T> List<Class<?>> getTypeArguments(Class<T> baseClass, Class<? extends T> childClass) {
		
		Map<Type, Type> resolvedTypes = new HashMap<Type, Type>();
		Type type = getNecessaryResolvedTypes(baseClass, childClass, resolvedTypes);

		// finally, for each actual type argument provided to baseClass,
		// determine (if possible)
		// the raw class for that type argument.
		Type[] actualTypeArguments;
		if (type instanceof Class) {
			actualTypeArguments = ((Class<?>) type).getTypeParameters();
		} else {
			actualTypeArguments = ((ParameterizedType) type)
					.getActualTypeArguments();
		}
		List<Class<?>> typeArgumentsAsClasses = new ArrayList<Class<?>>();
		// resolve types by chasing down type variables.
		for (Type baseType : actualTypeArguments) {
			while (resolvedTypes.containsKey(baseType)) {
				baseType = resolvedTypes.get(baseType);
			}
			typeArgumentsAsClasses.add(getClass(baseType));
		}
		return typeArgumentsAsClasses;
	}

    public static Class<?> getClassWithMark(Class<?> monitorClass, Class<?> mark) {

        Class<?>[] interfaces = monitorClass.getInterfaces();
        for(Class<?> interfaze : interfaces) {
            if(interfaze == mark) {
                return monitorClass;
            } else {
                Class<?> result = getClassWithMark(interfaze, mark);
                if(result != null) {
                    return result;
                }
            }
        }
        
        Class<?> superclass = monitorClass.getSuperclass();
        if(superclass == null) {
            return null;
        } else {
            return getClassWithMark(superclass, mark);
        }
    }
	
}
